package com.hxs.hxsserver.config.redis;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.ScanParams;
import redis.clients.jedis.ScanResult;

import java.util.*;

/**
 * @Author: Andi
 * @Description:
 * @Date: 2020/4/23 14:34
 */
@Service
@Slf4j
public class RedisService {

    @Autowired
    JedisPool jedisPool;

    /**
     * 获取单个对象
     *
     * @param prefix
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T get(KeyPrefix prefix, String key, Class<T> clazz) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + ":" + key;
            String str = jedis.get(realKey);
            return stringToBean(str, clazz);
        } finally {
            returnToPool(jedis);
        }
    }


    /**
     * 获取对象数组
     *
     * @param prefix
     * @param key
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> List<T> getList(KeyPrefix prefix, String key, Class<T> clazz) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            //生成真正的key
            String realKey = prefix.getPrefix() + ":" + key;
            String str = jedis.get(realKey);
            return stringToBeanArray(str, clazz);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 设置数组对象
     *
     * @param prefix
     * @param key
     * @param value
     * @param <T>
     * @return
     */
    public <T> Boolean setList(KeyPrefix prefix, String key, List<T> value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + ":" + key;
            String s = beanArrayToString(value);
            if (StringUtils.isBlank(s)) {
                return false;
            }
            int expireSecond = prefix.expireSeconds();
            if (expireSecond <= 0) {
                jedis.set(realKey, s);
            } else {
                jedis.setex(realKey, expireSecond, s);
            }
            return true;
        } finally {
            returnToPool(jedis);
        }
    }


    /**
     * 设置对象
     *
     * @param prefix
     * @param key
     * @param value
     * @param <T>
     * @return
     */
    public <T> Boolean set(KeyPrefix prefix, String key, T value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + ":" + key;
            String s = beanToString(value);
            if (StringUtils.isBlank(s)) {
                return false;
            }
            int expireSecond = prefix.expireSeconds();
            if (expireSecond <= 0) {
                jedis.set(realKey, s);
            } else {
                jedis.setex(realKey, expireSecond, s);
            }
            return true;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 设置对象
     *
     * @param prefix
     * @param key
     * @param value
     * @param <T>
     * @return
     */
    public <T> Boolean set(KeyPrefix prefix, String key, int expireSecond, T value) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + ":" + key;
            String s = beanToString(value);
            if (StringUtils.isBlank(s)) {
                return false;
            }
            if (expireSecond <= 0) {
                jedis.set(realKey, s);
            } else {
                jedis.setex(realKey, expireSecond, s);
            }
            return true;
        } finally {
            returnToPool(jedis);
        }
    }


    /**
     * 判断是否存在
     *
     * @param prefix
     * @param key
     * @return
     */
    public Boolean exists(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + ":" + key;
            return jedis.exists(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 获取有效时间
     *
     * @param prefix
     * @param key
     * @return
     */
    public Long ttl(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + ":" + key;
            return jedis.ttl(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 删除单个key
     *
     * @param prefix
     * @param key
     * @return
     */
    public Boolean delete(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + ":" + key;
            Long del = jedis.del(realKey);
            return del > 0;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 删除该key包下的所有key
     *
     * @param prefix
     * @return
     */
    public boolean delete(KeyPrefix prefix) {
        if (prefix == null) {
            return false;
        }
        List<String> keys = scanKeys(prefix.getPrefix());
        if (CollectionUtils.isEmpty(keys)) {
            return true;
        }
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            jedis.del(keys.toArray(new String[0]));
            return true;
        } catch (final Exception e) {
            e.printStackTrace();
            return false;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 增加值
     *
     * @param prefix
     * @param key
     * @param <T>
     * @return
     */
    public <T> Long incr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + ":" + key;
            Long incr = jedis.incr(realKey);
            if (incr == 1) {
                jedis.expire(realKey, prefix.expireSeconds());
            }
            return incr;
        } finally {
            returnToPool(jedis);
        }
    }

    /**
     * 减少值
     *
     * @param prefix
     * @param key
     * @param <T>
     * @return
     */
    public <T> Long decr(KeyPrefix prefix, String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            String realKey = prefix.getPrefix() + ":" + key;
            return jedis.decr(realKey);
        } finally {
            returnToPool(jedis);
        }
    }

    public List<String> scanKeys(String key) {
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            List<String> keys = new ArrayList<String>();
            String cursor = "0";
            ScanParams sp = new ScanParams();
            sp.match("*" + key + "*");
            sp.count(100);
            do {
                ScanResult<String> ret = jedis.scan(cursor, sp);
                List<String> result = ret.getResult();
                if (CollectionUtils.isNotEmpty(result)) {
                    keys.addAll(result);
                }
                //再处理cursor
                cursor = ret.getStringCursor();
            } while (!"0".equals(cursor));
            return keys;
        } finally {
            returnToPool(jedis);
        }
    }


    /**
     * 查询set集合数据
     *
     * @param key
     * @return
     */
    public <T> Set<T> getSet(Class<T> clazz, KeyPrefix prefix, String key) {
        key = prefix.getPrefix() + ":" + key;
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Set<String> smembers = jedis.smembers(key);
            Set<T> set = new HashSet<>();
            smembers.forEach(s -> set.add(JSON.parseObject(s, clazz)));
            return set;
        } catch (Exception e) {
            log.error(" get set error : " + e.getMessage());
        } finally {
            returnToPool(jedis);
        }
        return new HashSet<>();
    }

    /**
     * 往set中添加数据
     *
     * @param key
     * @param values
     * @return
     */
    public Long addSet(KeyPrefix prefix, String key, List<?> values) {
        if (values.isEmpty()) {
            delete(prefix, key);
            return 0L;
        }
        key = prefix.getPrefix() + ":" + key;
        List<String> objs = new ArrayList<>();
        values.forEach(o -> objs.add(JSON.toJSONString(o)));
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.sadd(key, objs.toArray(new String[0]));
        } catch (Exception e) {
            log.error(" add set error : " + e.getMessage());
        } finally {
            returnToPool(jedis);
        }
        return 0L;
    }

    /**
     * 删除数据
     *
     * @param key
     * @param values
     * @return
     */
    public Long delSet(KeyPrefix prefix, String key, List<?> values) {
        key = prefix.getPrefix() + ":" + key;
        List<String> objs = new ArrayList<>();
        values.forEach(o -> objs.add(JSON.toJSONString(o)));
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.srem(key, objs.toArray(new String[0]));
        } catch (Exception e) {
            log.error(" del set error : " + e.getMessage());
        } finally {
            returnToPool(jedis);
        }
        return 0L;
    }

    /**
     * 求key的合集
     *
     * @param keys
     * @return
     */
    public <T> Set<T> getUnionSet(Class<T> clazz, KeyPrefix prefix, List<String> keys) {
        List<String> newKeys = new ArrayList<>();
        keys.forEach(k -> newKeys.add(prefix.getPrefix() + ":" + k));
        String[] keyArray = newKeys.toArray(new String[0]);
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (keyArray.length == 1) {
                return getSet(clazz, prefix, keys.get(0));
            }
            Set<String> sunion = jedis.sunion(keyArray);
            Set<T> set = new HashSet<>();
            sunion.forEach(s -> set.add(JSON.parseObject(s, clazz)));
            return set;
        } catch (Exception e) {
            log.error(" get union set error : " + e.getMessage());
        } finally {
            returnToPool(jedis);
        }
        return new HashSet<>();
    }

    /**
     * 求key的交集
     *
     * @param keys
     * @return
     */
    public <T> Set<T> getInterSet(Class<T> clazz, KeyPrefix prefix, List<String> keys) {
        List<String> newKeys = new ArrayList<>();
        keys.forEach(k -> newKeys.add(prefix.getPrefix() + ":" + k));
        String[] keyArray = newKeys.toArray(new String[0]);
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            if (keyArray.length == 1) {
                return getSet(clazz, prefix, keys.get(0));
            }
            Set<String> sinter = jedis.sinter(keyArray);
            Set<T> set = new HashSet<>();
            sinter.forEach(s -> set.add(JSON.parseObject(s, clazz)));
            return set;
        } catch (Exception e) {
            log.error(" get inter set error : " + e.getMessage());
        } finally {
            returnToPool(jedis);
        }
        return new HashSet<>();
    }

    /**
     * Redis Zadd 命令用于将一个或多个成员元素及其分数值加入到有序集当中。
     * 如果某个成员已经是有序集的成员，那么更新这个成员的分数值，并通过重新插入这个成员元素，来保证该成员在正确的位置上。
     * 分数值可以是整数值或双精度浮点数。
     * 如果有序集合 key 不存在，则创建一个空的有序集并执行 ZADD 操作。
     * 当 key 存在但不是有序集类型时，返回一个错误。
     *
     * @param key
     * @param score
     * @param member
     * @return 被成功添加的新成员的数量，不包括那些被更新的、已经存在的成员。
     */
    public Long zadd(KeyPrefix prefix, String key, double score, String member) {
        key = prefix.getPrefix() + ":" + key;
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.zadd(key, score, member);
        } catch (Exception e) {
            log.error(" zadd set error : " + e.getMessage());
        } finally {
            returnToPool(jedis);
        }
        return null;
    }

    /**
     * Redis zrem 命令用于将一个或多个成员元素从有序集当中删除。
     *
     * @param key
     * @param member
     */
    public Long zrem(KeyPrefix prefix, String key, String... member) {
        key = prefix.getPrefix() + ":" + key;
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            return jedis.zrem(key, member);
        } catch (Exception e) {
            log.error(" zrem set error : " + e.getMessage());
        } finally {
            returnToPool(jedis);
        }
        return null;
    }

    /**
     * Redis Zrevrangebyscore 返回有序集中指定分数区间内的所有的成员。有序集成员按分数值递减(从大到小)的次序排列。
     * 具有相同分数值的成员按字典序的逆序(reverse lexicographical order )排列。
     * 除了成员按分数值递减的次序排列这一点外， ZREVRANGEBYSCORE 命令的其他方面和 ZRANGEBYSCORE 命令一样。
     *
     * @param key
     * @param max
     * @param min
     * @param offset
     * @param count
     * @return 指定区间内，带有分数值(可选)的有序集成员的列表。
     */
    public LinkedHashSet<String> zrevrangebyscore(KeyPrefix prefix, String key, String max, String min, int offset, int count) {
        key = prefix.getPrefix() + ":" + key;
        Jedis jedis = null;
        try {
            jedis = jedisPool.getResource();
            Set<String> strings = jedis.zrevrangeByScore(key, max, min, offset, count);
            return new LinkedHashSet<>(strings);
        } catch (Exception e) {
            log.error("zrevrangebyscore set error : " + e.getMessage());
        } finally {
            returnToPool(jedis);
        }
        return new LinkedHashSet<>();
    }

    public static <T> String beanToString(T value) {
        if (value == null) {
            return null;
        }
        Class<?> aClass = value.getClass();
        if (Objects.equals(aClass, int.class) || aClass == Integer.class) {
            return "" + value;
        } else if (aClass == Long.class) {
            return "" + value;
        } else if (aClass == String.class) {
            return (String) value;
        } else {
            return JSON.toJSONString(value);
        }
    }

    public static <T> String beanArrayToString(List<T> value) {
        if (value == null) {
            return null;
        }
        return JSON.toJSONString(value);
    }

    public static <T> T stringToBean(String str, Class<T> clazz) {
        if (StringUtils.isBlank(str) || clazz == null) {
            return null;
        }
        if (clazz == int.class || clazz == Integer.class) {
            return (T) Integer.valueOf(str);
        } else if (clazz == long.class || clazz == Long.class) {
            return (T) Long.valueOf(str);
        } else if (clazz == String.class) {
            return (T) str;
        } else {
            return JSON.parseObject(str, clazz);
        }
    }


    public static <T> List<T> stringToBeanArray(String str, Class<T> clazz) {
        if (StringUtils.isBlank(str) || clazz == null) {
            return null;
        }
        return JSON.parseArray(str, clazz);
    }


    private void returnToPool(Jedis jedis) {
        if (jedis != null) {
            jedis.close();
        }
    }

}
